package com.ydlclass;

import lombok.Data;

import java.util.concurrent.atomic.LongAdder;

/**
 * 请求id生成器：id发号器
 * 我们需要一个全局的id生成器,这个id生成器是线程安全的,
 * 并且是幂等的,也就是说,无论调用多少次,返回的id都是唯一的
 * 每一个服务都有一个唯一的id,这个id是全局唯一的
 * @Author duWenJian
 * @Date 2024/2/22 12:51
 * @Version 1.0
 */
@Data
public class IdGenerator {
    // 单机版的线程安全发号器,一旦变成了集群状态,就要改为分布式发号器
    //    private static LongAdder longAdder = new LongAdder();
    //    public static long getId() {
    //        longAdder.increment();
    //        return longAdder.sum();
    //    }
    /**
     * 雪花算法 64位
     * 机房号(数据中心)：5个bit  32
     * 机器号(机器标识)：5个bit  32
     * 时间戳(long型 1970-01-01 00:00:00 到当前时间的毫秒数)：42bit  2^41-1
     * 64位太长，所以我们把时间戳的部分截断，32，这样就能保证在一个毫秒内生成的id是连续的
     * 序列号(同一毫秒内，同一个机器，同一个数据中心，序列号是递增的)：10bit  2^10-1
     */
    // 起始时间戳
    private static final long START_TIME = DateUtils.get("2024-1-1").getTime();
    // 机房号
    private static final long DATA_CENTER_BIT = 5L;
    // 机器号
    private static final long MACHINE_BIT = 5L;
    // 序列号
    private static final long SEQUENCE_BIT = 12L;

    // 机房最大值 32
    private static final long DATA_CENTER_MAX = ~(-1L << DATA_CENTER_BIT);
    // 机器最大值 32
    private static final long MACHINE_MAX = ~(-1L << MACHINE_BIT);
    // 序列号最大值
    private static final long SEQUENCE_MAX = ~(-1L << SEQUENCE_BIT);

    // 组合数据最后通过位移、与运算符得到最终的id,组建成一个64bit的id
    // 时间戳(42bit)    机房号(5bit)   机器号(5bit)   序列号(12bit)
    // todo 左移会进行补零 ，比如 1左移22位，得到的结果是10000000000000000000000000000000000000000000000000000000000000000
    //
    /**
     * 时间戳需要左移22位
     */
    private static final long TIMESTAMP_LEFT = DATA_CENTER_BIT + MACHINE_BIT + SEQUENCE_BIT;
    /**
     * 机房号需要左移17位
     */
    private static final long DATA_CENTER_LEFT = MACHINE_BIT + SEQUENCE_BIT;
    /**
     * 机器号需要左移12位
     */
    private static final long MACHINE_LEFT = SEQUENCE_BIT;
    /**
     * 序列号不需要左移
     */
    private static final long SEQUENCE_LEFT = 0L;

    private long dataCenterId;
    private long machineId;
    private LongAdder sequenceId = new LongAdder();

    // todo 时钟回拨的问题之后在处理
    private long lastTimeStamp = -1L;

    public IdGenerator(long dataCenterId, long machineId) {
        if (dataCenterId > DATA_CENTER_MAX || machineId > MACHINE_MAX){
            throw new RuntimeException("您传入的数据中心或机器号不合法!");
        }
        this.dataCenterId = dataCenterId;
        this.machineId = machineId;
    }

    public long getId() {
        long currentTimeStamp = System.currentTimeMillis();
        long timeStamp = currentTimeStamp - START_TIME;

        // 判断时钟回拨
        if (timeStamp < lastTimeStamp) {
            throw new RuntimeException("Clock moved backwards.  Refusing to generate id");
        }

        // 在相同的时间戳的情况下,会有重复的
        if (timeStamp == lastTimeStamp){
            sequenceId.increment();
            // 序列号溢出
            if (sequenceId.sum() > SEQUENCE_MAX){
                timeStamp = getNextTimeStamp();
                sequenceId.reset();
            }
        } else {
            sequenceId.reset();
        }
        // 时间戳
        lastTimeStamp = timeStamp;
        long sequence = sequenceId.sum();
        return timeStamp << TIMESTAMP_LEFT | dataCenterId << DATA_CENTER_LEFT
                | machineId << MACHINE_LEFT | sequence;
    }

    /**
     * 获取下一个时间戳
     * @return
     */
    private long getNextTimeStamp() {
        while (true){
            long current = System.currentTimeMillis() - START_TIME;
            if (current != lastTimeStamp){
                return current;
            }
        }
    }

    public static void main(String[] args) {
        // 生成100次
        for (int i = 0; i < 100; i++) {
            System.out.println(new IdGenerator(1, 1).getId());
        }
    }
}
